home *** CD-ROM | disk | FTP | other *** search
- #import "MultDoc.h"
- #import "MultApp.h"
- #import <appkit/appkit.h>
-
- @implementation MultDoc: Responder
-
- /* Canon Information Systems is not responsible for anything anyone does with this */
- /* code, nor are they responsible for the correctness of this code. Basically, this */
- /* has very little to do with the company I work for, and you can't blame them. */
-
- /* This file is best read in a window as wide as this comment, and with tab settings */
- /* of 4 spaces and indent setting of 4 spaces (at least, that's what I use). */
-
- /* You are welcome to do as you would with this file under the following conditions. */
- /* First, I accept no blame for anything that goes wrong no matter how you use it, */
- /* no matter how catastrophic, not even if it stems from a bug in my code. */
- /* Second, please keep my notices on it when/if you distribute it. */
- /* Third, if you discover any bugs or have any comments, PLEASE TELL ME! Code won't */
- /* get better without people picking it apart and giving the writer feedback. */
- /* Fourth, if you modify it, please keep a notice that your version is based on mine */
- /* in the source files (and keep the notice that mine is based on four other pieces */
- /* of code :<). Thanks, and have fun. - Subrata Sircar, ssircar@canon.com */
-
- /* This class should handle all the document-level responsibilities */
- /* that a nice document in a multi-doc environment should handle: */
- /* creation, deletion, keeping track of dirty bits, not closing w/out */
- /* allowing the user to save, etc. Most of this code is lifted from */
- /* DrawDocument, or TextLab; heavy use is made of the firstResponder */
- /* mechanism. Thanks again, guys. */
-
- /* The default implementation loads windows with scrollviews from a nib file. */
- /* To modify, the revert/save/newFromFile methods should be changed to use */
- /* the view (subclass) that acts as the content view of the Window. */
- /* - Subrata Sircar (ssircar@canon.com) */
-
- /* Version 0.9b Apr-19-92 First Public Release */
- /* Version 0.95b Apr-29-92 Multiple Save Types */
- /* Version 1.0b Aug-10-92 Minor Bug Fixes */
-
- /* Factory (Class) variables */
-
- static char *default_format = NULL; /* Format string for default title */
- static const char *extension = NULL; /* Document extension */
- static const int myVersion = 100; /* version number * 100 */
- static id zoneList = nil;
- static NXCoord ORIGX = 100.0;/* doc starting position and increment */
- static NXCoord ORIGY = 100.0;
- static int posInc = 20.0;
-
- + initialize
- /* Class variable initialization. DO NOT call from subclasses. */
- {
- if (self == [MultDoc class]) {
- [self setVersion:myVersion];
- [self setExtension:LocalString("txt")];
- [self setDefault:LocalString("Untitled%d")];
- }
- return self;
- }
-
- /* Extension to use in Workspace file names */
-
- + setExtension:(const char *)newExtension
- {
- if (extension) NX_FREE(extension);
- extension = NXCopyStringBufferFromZone(newExtension,MyZone);
- return self;
- }
-
- + (const char *)extension
- {
- return extension;
- }
-
- /* Default Title string for new documents */
-
- + setDefault:(const char *)newDefault
- {
- char temp[MAXPATHLEN+1];
-
- if (default_format) NX_FREE(default_format);
- sprintf(temp,"%s.%s",newDefault,[[self class] extension]);
- default_format = NXCopyStringBufferFromZone(temp,MyZone);
- return self;
- }
-
- + (const char *)default
- {
- return default_format;
- }
-
- /* Factory zone methods. I adopt the virtual zone strategy used by Draw. */
-
- + (NXZone *)newZone
- {
- if (!zoneList || ![zoneList count]) {
- return NXCreateZone(vm_page_size, vm_page_size, YES);
- } else {
- return (NXZone *)[zoneList removeLastObject];
- }
- }
-
- + (void)reuseZone:(NXZone *)aZone
- {
- if (!zoneList) zoneList = [List new];
- [zoneList addObject:(id)aZone];
- NXNameZone(aZone, "Unused");
- }
-
- + allocFromZone:(NXZone *)aZone
- {
- return [self notImplemented:@selector(allocFromZone:)];
- }
-
- + alloc
- {
- return [self notImplemented:@selector(alloc)];
- }
-
-
- /* New creation methods */
-
- - setUpNib
- {
- LoadLocalNib(LocalString("MultDoc.nib"),self,NO,MyZone);
- [[view docView] setDelegate:self];
- [window makeFirstResponder:[view docView]];
- return self;
- }
-
- - instanceAwake
- {
- NXRect frameRect;
- calcFrame(printInfo, &frameRect);
- if (frameRect.size.width > 1000) frameRect.size.width = 1000;
- if (frameRect.size.height > 700) frameRect.size.height = 700;
- [window sizeWindow:frameRect.size.width :frameRect.size.height];
- [window moveTo:ORIGX :ORIGY];
- ORIGX += posInc; ORIGY -= posInc;
- if (ORIGX > 700) ORIGX = 110;
- if (ORIGY < 100) ORIGY = 160;
- [window setDelegate:self];
- [self dirty:NO];
- [self setSavedDocument:NO];
- [self setEmpty:YES];
- [self setName:NULL andDirectory:NULL];
- [window makeKeyAndOrderFront:self];
- return self;
- }
-
- + newFromFile:(const char *)file
- {
- NXStream *stream;
-
- stream = NXMapFile(file,NX_READONLY);
- if (stream) {
- self = [self new];
- [[view docView] readText:stream];
- [[view docView] sizeToFit];
- [view display];
- [self setName:file];
- [self setSavedDocument:YES];
- [self setEmpty:NO];
- [window makeKeyAndOrderFront:self];
- NXCloseMemory(stream,NX_FREEBUFFER);
- return self;
- } else {
- Notify(LocalString("MultDoc: can't open file"),file);
- return nil;
- }
- }
-
- + new
- {
- self = [[self class] newFromZone:[[self class] newZone]];
- return self;
- }
-
- + newFromZone:(NXZone *)zone;
- {
- self = [super allocFromZone:zone];
- printInfo = [PrintInfo new];
- [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
- [self setUpNib];
- [self instanceAwake];
- return self;
- }
-
- - free
- {
-
- NX_FREE(name);
- NX_FREE(directory);
- [printInfo free];
- [window free];
- [[self class] reuseZone:MyZone];
- return [super free];
- }
-
- - view
- /*
- * Returns the view associated with this document.
- */
- {
- return view;
- }
-
- - (BOOL)needsSaving
- /*
- * Returns the dirty BOOL associated with this document.
- */
- {
- return dirty;
- }
-
- - (BOOL)isEmpty
- /*
- * Returns the empty BOOL associated with this document.
- */
- {
- return empty;
- }
-
- - (BOOL)hasSavedDocument
- {
- return haveSavedDocument;
- }
-
- - setEmpty:(BOOL)flag
- /*
- * Sets the BOOL to flag.
- */
- {
- empty = flag;
- return self;
- }
-
- - dirty:(BOOL)flag
- /*
- * Sets the BOOL to flag.
- */
- {
- dirty = flag;
- [window setDocEdited:flag];
- return self;
- }
-
- - setSavedDocument:(BOOL)flag
- /*
- * Sets the BOOL to flag.
- */
- {
- haveSavedDocument = flag;
- return self;
- }
-
- - printInfo
- {
- return printInfo;
- }
-
-
- /* Target/Action methods */
-
- - save:sender
- /*
- * Saves the file. If this document has never been saved to disk,
- * then a SavePanel is put up to ask the user what file name she
- * wishes to use to save the document.
- */
- {
- id del = [NXApp delegate];
- id savepanel = [del saveAsPanel:self];
- const char *dir = NULL;
-
- if (![self hasSavedDocument]) {
- if ([savepanel runModalForDirectory:directory file:name])
- [self setName:[savepanel filename]];
- else return nil;
- }
-
- [self save];
- dir = [self directory];
- chdir(dir);
- [del setDefaultDir:dir];
- return self;
- }
-
- - saveAs:sender
- /* Changes the document name. IF you cancel, it does the right thing */
- /* and undoes the changes to the state bits. */
- {
- BOOL oldSavedFile, oldDirty;
-
- oldDirty = [self needsSaving];
- oldSavedFile = [self hasSavedDocument];
- [self dirty:YES];
- [self setSavedDocument:NO];
- if ([self save:sender]) return self;
- else {
- [self setSavedDocument:oldSavedFile];
- [self dirty:oldDirty];
- return nil;
- }
- }
-
- - revertToSaved:sender
- /*
- * Revert the document back to what is on the disk.
- */
- {
- MultDoc *oldPtr;
-
- if (![self hasSavedDocument] ||
- ![self needsSaving] ||
- (NXRunAlertPanel(LocalString("Revert"),
- LocalString("%s has been edited. Are you sure you want to undo changes?"),
- LocalString("Revert"),
- LocalString("Cancel"), NULL, name) != NX_ALERTDEFAULT)) {
- return self;
- }
-
- oldPtr = self;
- self = [[oldPtr class] newFromFile:[oldPtr fileName]];
- [oldPtr free];
- return self;
- }
-
- - save
- {
- NXStream *ts;
- int fd;
-
- fd = open([self fileName], O_CREAT | O_WRONLY | O_TRUNC, 0666);
- ts = NXOpenFile(fd, NX_WRITEONLY);
- if (ts) {
- [[view docView] writeText:ts];
- NXFlush(ts);
- NXClose(ts);
- [self dirty:NO];
- [self setSavedDocument:YES];
- } else Notify(LocalString("MultDoc:save - can't create file"),[self fileName]);
- close(fd);
- return self;
- }
-
- - changeLayout:sender
- /*
- * Puts up a PageLayout panel and allows the user to pick a different
- * size paper to work on. After she does so, the view is resized to the
- * new paper size.
- * Since the PrintInfo is effectively part of the document, we dirty
- * the view (by performing the dirty method).
- */
- {
- NXRect frame;
-
- if ([[[NXApp delegate] pageLayout:self] runModal] == NX_OKTAG) {
- calcFrame(printInfo, &frame);
- [window sizeWindow:frame.size.width :frame.size.height];
- [self dirty:YES];
- [window display];
- }
-
- return self;
- }
-
-
- /* Methods related to naming/saving this document. */
-
- - (const char *)fileName
- /*
- * Gets the fully specified file name of the document.
- * If directory is NULL, then the currentDirectory is used.
- * If name is NULL, then the default title is used.
- */
- {
- static char filenamebuf[MAXPATHLEN+1];
-
- if (!directory && !name) [self setName:NULL andDirectory:NULL];
- if (directory) {
- strcpy(filenamebuf, directory);
- strcat(filenamebuf, "/");
- } else filenamebuf[0] = '\0';
- if (name) strcat(filenamebuf, name);
-
- return filenamebuf;
- }
-
- - (const char *)directory
- {
- return directory;
- }
-
- - (const char *)name
- {
- return name;
- }
-
- - setName:(const char *)newName andDirectory:(const char *)newDirectory
- /*
- * Updates the name and directory of the document.
- * newName or newDirectory can be NULL, in which case the name or directory
- * will not be changed (unless one is currently not set, in which case
- * a default name will be used).
- */
- {
- char newNameBuf[MAXPATHLEN+1];
- static int uniqueCount = 1;
-
- if ((newName && *newName) || !name) {
- if (!newName || !*newName) {
- sprintf(newNameBuf, [[self class] default], uniqueCount++);
- newName = newNameBuf;
- } else if (name) NX_FREE(name);
- name = NXCopyStringBufferFromZone(newName, MyZone);
- }
-
- if ((newDirectory && (*newDirectory == '/')) || !directory) {
- if (!newDirectory || (*newDirectory != '/')) newDirectory = [[NXApp delegate] currentDirectory];
- else if (directory) NX_FREE(directory);
- directory = NXCopyStringBufferFromZone(newDirectory, MyZone);
- }
-
- [window setTitleAsFilename:[self fileName]];
- NXNameZone(MyZone, [self fileName]);
-
- return self;
- }
-
- - setName:(const char *)file
- /*
- * If file is a full path name, then both the name and directory of the
- * document is updated appropriately, otherwise, only the name is changed.
- */
- {
- char *lastComponent;
- char path[MAXPATHLEN+1];
-
- if (file) {
- strcpy(path, file);
- lastComponent = rindex(path, '/');
- if (lastComponent) {
- *lastComponent++ = '\0';
- return [self setName:lastComponent andDirectory:path];
- } else return [self setName:file andDirectory:NULL];
- }
-
- return self;
- }
-
- /* Window delegate methods. */
-
- - windowWillClose:sender
- /*
- * If the document has been edited, then this asks the user if she
- * wants to save the changes before closing the window. When the window
- * is closed, the document itself must be freed. This is accomplished
- * via Application's delayedFree: mechanism. Unfortunately, by the time
- * delayedFree: frees the document, the window and view instance variables
- * will already have automatically been freed by virtue of the window's being
- * closed. Thus, those instance variables must be set to nil to avoid their
- * being freed twice. (This behavior is set in the window flags in IB.)
- *
- * Returning nil from this method informs the caller that the window should
- * NOT be closed. Anything else implies it should be closed.
- */
- {
- int save;
-
- if ([self needsSaving] && ![self isEmpty]) {
- [window orderFront:self];
- save = NXRunAlertPanel(LocalString("Close"),
- LocalString("%s has changes. Save them?"),
- LocalString("Save"),
- LocalString("Don't Save"),
- LocalString("Cancel"),
- name);
- if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) return nil;
- else {
- [window endEditingFor:self]; /* terminate any editing */
- if (save == NX_ALERTDEFAULT) {
- if (![self save:nil]) return nil;
- }
- }
- }
-
- window = nil;
- view = nil;
- if (printInfo) [NXApp setPrintInfo:nil];
- [NXApp delayedFree:self];
- chdir([[NXApp delegate] currentDirectory]);
- return self;
- }
-
- - windowDidBecomeMain:sender
- /*
- * Switch the Application's PrintInfo to the document's when the document
- * window becomes the main window. Also set the cursor appropriately
- * depending on which tool is currently selected.
- */
- {
- [NXApp setPrintInfo:printInfo];
- return self;
- }
-
- - windowWillMiniaturize:sender toMiniwindow:counterpart
- {
- char *dot;
- char title[MAXPATHLEN+1];
-
- strcpy(title, [self name]);
- dot = rindex(title, '.');
- if (dot && !strcmp(++dot, extension)) *(--dot) = '\0';
- [counterpart setTitle:title];
- return self;
- }
-
-
- /* Validates whether a menu command makes sense now */
-
- - (BOOL)validateCommand:menuCell
- {
- SEL action = [menuCell action];
-
- if (action == @selector(revertToSaved:)) return ([self needsSaving] && [self hasSavedDocument]);
- else if (action == @selector(saveAs:)) return (![self isEmpty] && [self hasSavedDocument]);
- else if (action == @selector(close:)) return YES;
-
- return YES;
- }
-
- /* Text Delegate methods */
-
- - textDidGetKeys:sender isEmpty:(BOOL)flag
- {
-
- if (![self needsSaving]) [self dirty:YES];
- [self setEmpty:flag];
- return self;
- }
-
- @end
-